<script setup>
import nodata from "@/assets/img/no-data.png";
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue"
import {Delete, InfoFilled} from "@element-plus/icons-vue";
import BlackSelect from "@/components/ui/BlackSelect.vue";
import BlackSwitch from "@/components/ui/BlackSwitch.vue";
import BlackInput from "@/components/ui/BlackInput.vue";
import MusicPlayer from "@/components/MusicPlayer.vue";
import {compact} from "lodash";
import {httpDownload, httpGet, httpPost} from "@/utils/http";
import {showMessageError, showMessageOk} from "@/utils/dialog";
import {checkSession, getClientId} from "@/store/cache";
import {ElMessage, ElMessageBox} from "element-plus";
import {formatTime, replaceImg} from "@/utils/libs";
import Clipboard from "clipboard";
import BlackDialog from "@/components/ui/BlackDialog.vue";
import Compressor from "compressorjs";
import Generating from "@/components/ui/Generating.vue";
import {useSharedStore} from "@/store/sharedata";

// 是否自定义模式
const custom = ref(false)
// 可选的模型列表
const models = ref([
  { label: "v3.0", value: "chirp-v3-0" },
  { label: "v3.5", value: "chirp-v3-5" },
  { label: "v4.0", value: "chirp-v4" },
])
// 音乐风格标签库
const tags = ref([
  {label: "女声", value: "female vocals"},
  {label: "男声", value: "male vocals"},
  {label: "流行", value: "pop"},
  {label: "摇滚", value: "rock"},
  {label: "硬摇滚", value: "hard rock"},
  {label: "电音", value: "electronic"},
  {label: "金属", value: "metal"},
  {label: "重金属", value: "heavy metal"},
  {label: "节拍", value: "beat"},
  {label: "弱拍", value: "upbeat"},
  {label: "合成器", value: "synth"},
  {label: "吉他", value: "guitar"},
  {label: "钢琴", value: "piano"},
  {label: "小提琴", value: "violin"},
  {label: "贝斯", value: "bass"},
  {label: "嘻哈", value: "hip hop"},
])
// 创作参数对象
const data = ref({
  client_id: getClientId(),
  model: 'chirp-v3-0', // 模型
  tags: '', // 歌曲风格
  lyrics: '', // 歌词
  prompt: '', // 提示词
  title: '', // 歌曲名称
  instrumental: false, // 是否纯音乐
  ref_task_id: '', // 续写参考的任务ID
  extend_secs: 0,  // 续写起始时间(秒)
  ref_song_id: ''  // 续写参考的歌曲ID
})

const loading = ref(false)
// 是否无数据状态
const noData = ref(true)
// 播放列表
const playList = ref([])
// 播放器组件引用
const playerRef = ref(null)
// 播放器显示状态
const showPlayer = ref(false)
// 作品列表
const list = ref([])
// 创作按钮文本
const btnText = ref('开始创作')
// 续写参考歌曲
const refSong = ref(null)
// 编辑对话框显示状态
const showDialog = ref(false)
// 编辑歌曲数据
const editData = ref({title: '', cover: '', id: 0})
// 歌词输入框提示
const promptPlaceholder = ref('请在这里输入你自己写的歌词...')
// 当前正在播放音乐的索引
const songIndex = ref(0)
const store = useSharedStore()
const clipboard = ref(null)
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)

onMounted(() => {
  clipboard.value = new Clipboard('.copy-link')
  clipboard.value.on('success', () => {
    showMessageOk('复制歌曲链接成功！')
  })
  clipboard.value.on('error', () => {
    showMessageError('复制失败！')
  })

  checkSession().then(() => {
    fetchData(1)
  }).catch(() => {
  })

  store.addMessageHandler('suno', (data) => {
    console.log(data)
    // 丢弃无关消息
    if (data.channel !== "suno" || data.clientId !== getClientId()) {
      return
    }
    if (data.body === "FINISH" || data.body === "FAIL") {
      fetchData(1)
    }
  })
})

onUnmounted(() => {
  clipboard.value.destroy()
  store.removeMessageHandler('suno')
})

// 获取作品列表
const fetchData = (_page) => {
  if (_page) {
    page.value = _page
  }
  loading.value = true
  httpGet('api/suno/list', {page: page.value, page_size: pageSize.value}).then(res => {
    total.value = res.data.total
    const items = []
    for (let v of res.data.items) {
      if (v.progress === 100) {
        // 设置生成的模型
        v.major_model_version = v['raw_data']['major_model_version']
      }
      items.push(v)
    }
    loading.value = false
    list.value = items
    noData.value = list.value.length === 0
  }).catch(e => {
    loading.value = false
    noData.value = true
    showMessageError('获取作品列表失败：' + e.message)
  })
}

// 创作新的歌曲
const create = () => {
  data.value.type = custom.value ? 2 : 1
  // 设置续写参数
  data.value.ref_task_id = refSong.value ? refSong.value.task_id : ''
  data.value.ref_song_id = refSong.value ? refSong.value.song_id : ''
  data.value.extend_secs = parseInt(refSong.value ? refSong.value.extend_secs : 0)
  if (refSong.value) {
    // 判断续写的其实时间是否大于续写歌曲的时间
    if (data.value.extend_secs > refSong.value.duration) {
      return showMessageError('续写开始时间不能超过原歌曲长度')
    }
  } else if (custom.value) { // 判断是不是自定义模式
    // 如果是自定义模式歌词不能为空
    if (data.value.lyrics === '') {
      return showMessageError('请输入歌词')
    }
    // 标题不能为空
    if (data.value.title === '') {
      return showMessageError('请输入歌曲标题')
    }
  } else {
    // 提示词不能为空
    if (data.value.prompt === '') {
      return showMessageError('请输入歌曲描述')
    }
  }

  console.log(data.value)
  httpPost('api/suno/create', data.value).then(() => {
    fetchData(1)
    showMessageOk('创建任务成功')
  }).catch(e => {
    showMessageError('创建任务失败：' + e.message)
  })
}

// 合并歌曲
const merge = (item) => {
  httpPost('api/suno/create', {song_id: item.song_id, type: 3,client_id: getClientId()}).then(() => {
    fetchData(1)
    showMessageOk('创建任务成功')
  }).catch(e => {
    showMessageError('合并歌曲失败：' + e.message)
  })
}

// 下载歌曲
const download = (item) => {
  // 处理音频URL
  const url = replaceImg(item.audio_url)
  // 构建下载URL
  const downloadURL = `${import.meta.env.VITE_VUE_APP_API_HOST}/api/file/download?url=${url}`

  // 从原始音频URL中提取文件名。通过创建一个URL对象，然后获取其路径（pathname），再通过split('/')分割路径，最后用pop()获取最后一个部分作为文件名。
  const urlObj = new URL(url)
  const fileName = urlObj.pathname.split('/').pop()
  // 用于在UI上显示下载中的状态
  item.downloading = true
  // 发送下载请求
  httpDownload(downloadURL).then(res => {
    // 创建Blob对象
    const blob = new Blob([res.data])
    // 创建下载链接
    const link = document.createElement('a')
    link.href = URL.createObjectURL(blob)
    link.download = fileName
    document.body.appendChild(link)
    // 触发下载
    link.click()
    // 清理资源
    document.body.removeChild(link)
    URL.revokeObjectURL(link.href)
    item.downloading = false
  }).catch(e => {
    showMessageError('下载失败：' + e.message)
    item.downloading = false
  })
}

// 上传音频文件
const uploadAudio = (file) => {
  const formData = new FormData()
  formData.append('file', file.file, file.name)
  // 执行上传操作
  httpPost('api/file/upload', formData).then(res => {
    httpPost('api/suno/create', {audio_url: res.data.url, title: res.data.name, type: 4,client_id: getClientId()}).then(() => {
      fetchData(1)
      showMessageOk('歌曲上传成功')
    }).catch(e => {
      showMessageError('歌曲上传失败：' + e.message)
    })
    removeRefSong()
    ElMessage.success({message: '上传成功', duration: 500})
  }).catch(e => {
    showMessageError('文件上传失败：' + e.message)
  })
}

// 续写歌曲
const extend = (item) => {
  refSong.value = item
  refSong.value.extend_secs = item.duration
  data.value.title = item.title
  custom.value = true
  btnText.value = '续写歌曲'
  promptPlaceholder.value = '输入额外的歌词，根据您之前的歌词来扩展歌曲...'
}

// 打开更新歌曲弹框
const update = (item) => {
  showDialog.value = true
  editData.value.title = item.title
  editData.value.cover = item.cover_url
  editData.value.id = item.id
}

// 更新歌曲
const updateSong = () => {
  if (editData.value.title === '' || editData.value.cover === '') {
    return showMessageError('歌曲标题和封面不能为空')
  }
  httpPost('api/suno/update', editData.value).then(() => {
    showMessageOk('更新歌曲成功')
    showDialog.value = false
    fetchData()
  }).catch(e => {
    showMessageError('更新歌曲失败：' + e.message)
  })
}

// 监听custom是否发生变化
watch(() => custom.value, (newVal) => {
  if (!newVal) {
    removeRefSong()
  }
})

// 移除引用的续写歌曲
const removeRefSong = () => {
  refSong.value = null
  btnText.value = '开始创作'
  promptPlaceholder.value = '请在这里输入你自己写的歌词...'
}

// 播放音乐
const play = (item,index) => {
  songIndex.value = index
  playList.value = list.value
  // 显示播放音乐组件
  showPlayer.value = true
  // 播放音频
  nextTick(() => playerRef.value.play())
}

// 选择风格标签
const selectTag = (tag) => {
  if (data.value.tags.length + tag.value.length >= 119) {
    return
  }
  data.value.tags = compact([...data.value.tags.split(','), tag.value]).join(',')
}

const removeJob = (item) => {
  ElMessageBox.confirm(
      '此操作将会删除任务相关文件，继续操作吗?',
      '删除提示',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    httpGet('api/suno/remove', {id: item.id}).then(() => {
      showMessageOk('任务删除成功')
      fetchData()
    }).catch(e => {
      showMessageError('任务删除失败：' + e.message)
    })
  }).catch(() => {
  })
}

// 发布作品
const publishJob = (item) => {
  httpGet('api/suno/publish', {id: item.id, publish: item.publish}).then(() => {
    showMessageOk('操作成功')
  }).catch(e => {
    showMessageError('操作失败：' + e.message)
  })
}

// 生成分享链接
const getShareURL = (item) => {
  return `${location.protocol}//${location.host}/song/${item.song_id}`
}

// 上传封面图片
const uploadCover = (file) => {
  // 压缩图片并上传
  new Compressor(file.file, {
    quality: 0.6,
    success(result) {
      const formData = new FormData()
      formData.append('file', result, result.name)
      // 执行上传操作
      httpPost('api/file/upload', formData).then((res) => {
        editData.value.cover = res.data.url
        showMessageOk({message: '上传成功', duration: 500})
      }).catch(e => {
        showMessageError('图片上传失败：' + e.message)
      })
    },
    error(error) {
      console.log('压缩图片出现错误：', error)
    }
  })
}

const isGenerating = ref(false)
// 生成歌词
const createLyric = () => {
  if (data.value.lyrics === '') {
    return showMessageError('请输入歌词描述')
  }
  isGenerating.value = true
  httpPost('api/prompt/lyric', {prompt: data.value.lyrics}).then(res => {
    // 按换行符拆分成数组
    const lines = res.data.split('\n')
    // shift() 方法从数组中删除第一个元素，并返回该元素的值
    // 第一行作为歌曲标题，并移除可能存在的星号（用空字符串替换掉）
    data.value.title = lines.shift().replace(/\*/g, "")
    // 移除第二行（假设第二行为空行，或者我们不想要的一行，比如标题与歌词之间的分隔行）
    lines.shift()
    // 换行符连接，作为歌词内容
    data.value.lyrics = lines.join('\n')
    isGenerating.value = false
  }).catch(e => {
    showMessageError('歌词生成失败：' + e.message)
    isGenerating.value = false
  })
}
</script>

<template>
  <div class="page-suno">
    <div class="left-bar">
      <div class="bar-top">
        <el-tooltip content="定义模式" placement="top">
          <black-switch v-model:value="custom" size="large"/>
        </el-tooltip>
        <el-tooltip content="请上传6-60秒的原始音频，检测到人声的音频将仅设为私人音频。"
                    placement="bottom-end">
          <el-upload
              class="avatar-uploader"
              :auto-upload="true"
              :show-file-list="false"
              :http-request="uploadAudio"
              accept=".wav,.mp3"
          >
            <el-button class="upload-music" type="primary" round>
              <i class="iconfont icon-upload"></i>
              <span>上传音乐</span>
            </el-button>
          </el-upload>
        </el-tooltip>
        <black-select v-model:value="data.model" :options="models" placeholder="请选择模型" style="width: 100px"/>
      </div>

      <div class="params">
        <div class="pure-music">
          <span class="switch"><black-switch v-model:value="data.instrumental" size="default"/></span>
          <span class="text">纯音乐</span>
        </div>

        <div v-if="custom">
          <div class="item-group" v-if="!data.instrumental">
            <div class="label">
              <span class="text">歌词</span>
              <el-popover placement="right" :width="200" trigger="hover"
                          content="自己写歌词或寻求 AI 的帮助。使用两节歌词（8 行）可获得最佳效果。">
                <template #reference>
                  <el-icon>
                    <InfoFilled/>
                  </el-icon>
                </template>
              </el-popover>
            </div>
            <div class="item" v-loading="isGenerating" element-loading-text="正在生成歌词...">
              <black-input v-model:value="data.lyrics" type="textarea" :rows="10" :placeholder="promptPlaceholder"/>
              <button class="btn btn-lyric" @click="createLyric">生成歌词</button>
            </div>
          </div>

          <div class="item-group">
            <div class="label">
              <span class="text">音乐风格</span>
              <el-popover placement="right" :width="200" trigger="hover"
                          content="描述您想要的音乐风格（例如“原声流行音乐”）。Sunos 模特无法识别艺术家的名字，但能够理解音乐流派和氛围。">
                <template #reference>
                  <el-icon>
                    <InfoFilled/>
                  </el-icon>
                </template>
              </el-popover>
            </div>
            <div class="item">
              <black-input v-model:value="data.tags" type="textarea" :maxlength="120" :rows="3"
                           placeholder="请输入音乐风格，多个风格之间用英文逗号隔开..."/>
            </div>
            <div class="tag-select">
              <div class="inner">
                <span class="tag" @click="selectTag(tag)" v-for="tag in tags" :key="tag.value">{{ tag.label }}</span>
              </div>
            </div>
          </div>

          <div class="item-group">
            <div class="label">
              <span class="text">歌曲名称</span>
              <el-popover placement="right" :width="200" trigger="hover"
                          content="给你的歌曲起一个标题，以便于分享、发现和组织。">
                <template #reference>
                  <el-icon>
                    <InfoFilled/>
                  </el-icon>
                </template>
              </el-popover>
            </div>
            <div class="item">
              <black-input v-model:value="data.title" type="textarea" :rows="1"
                           placeholder="请输入歌曲名称..."/>
            </div>
          </div>
        </div>

        <div v-else>
          <div class="label">
            <span class="text">歌曲描述</span>
            <el-popover placement="right" :width="200" trigger="hover"
                        content="描述您想要的音乐风格和主题（例如：关于假期的流行音乐）。请使用流派和氛围，而不是特定的艺术家和歌曲风格，AI无法识别。">
              <template #reference>
                <el-icon>
                  <InfoFilled/>
                </el-icon>
              </template>
            </el-popover>
          </div>
          <div class="item">
            <black-input v-model:value="data.prompt" type="textarea" :rows="10"
                         placeholder="例如：一首关于爱情的摇滚歌曲..."/>
          </div>
        </div>

        <div class="ref-song" v-if="refSong">
          <div class="label">
            <span class="text">续写</span>
            <el-popover placement="right"
                        :width="200"
                        trigger="hover" content="输入额外的歌词，根据您之前的歌词来扩展歌曲。">
              <template #reference>
                <el-icon>
                  <InfoFilled/>
                </el-icon>
              </template>
            </el-popover>
          </div>
          <div class="item">
            <div class="song">
              <el-image :src="refSong.cover_url" fit="cover"/>
              <span class="title">{{ refSong.title }}</span>
              <el-button type="info" @click="removeRefSong" size="small" :icon="Delete" circle/>
            </div>
            <div class="extend-secs">
              从 <input v-model="refSong.extend_secs" type="text"/> 秒开始续写
            </div>
          </div>
        </div>

        <div class="submit-btn">
          <el-button type="primary" @click="create" :dark="false" round>
            <img src="/images/create-new.svg" alt=""/>
            <span>{{ btnText }}</span>
          </el-button>
        </div>
      </div>
    </div>

    <!-- 显示作品列表 -->
    <div class="right-box  h-dvh" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
      <div class="list-box" v-if="!noData">
        <div v-for="(item,index) in list" :key="item.id">
          <!-- 完成任务展示 -->
          <div class="item" v-if="item.progress === 100">
            <!-- 作品封面和时长  -->
            <div class="left">
              <div class="container">
                <el-image :src="item.cover_url" fit="cover"/>
                <div class="duration">{{ formatTime(item.duration) }}</div>
                <button class="play" @click="play(item,index)">
                  <img src="/images/play.svg" style="" alt=""/>
                </button>
              </div>
            </div>
            <!-- 设置作品信息 -->
            <div class="center">
              <div class="title">
                <a :href="'/song/'+item.song_id" target="_blank">{{ item.title }}</a>
                <span class="model" v-if="item.major_model_version">{{ item.major_model_version }}</span>
                <span class="model" v-if="item.type === 4">用户上传</span>
                <span class="model" v-if="item.type === 3">
                  <i class="iconfont icon-mp3"></i>
                  完整歌曲
                </span>
                <span class="model" v-if="item.ref_song">
                  <i class="iconfont icon-link"></i>
                  {{ item.ref_song.title }}
                </span>
              </div>
              <div class="tags" v-if="item.tags">{{ item.tags }}</div>
            </div>
            <!-- 作品操作 -->
            <div class="right">
              <div class="tools grid grid-flow-row auto-rows-max">
                <el-tooltip content="以当前歌曲为素材继续创作" placement="top">
                  <button class="btn" @click="extend(item)">续写</button>
                </el-tooltip>
                <button class="btn btn-publish">
                  <span class="text">发布</span>
                  <black-switch v-model:value="item.publish" @change="publishJob(item)" size="small"/>
                </button>
                <el-tooltip content="下载歌曲" placement="top">
                  <button class="btn btn-icon" @click="download(item)">
                    <i class="iconfont icon-download" v-if="!item.downloading"></i>
                    <el-image src="/images/loading.gif" class="downloading" fit="cover" v-else/>
                  </button>
                </el-tooltip>
                <el-tooltip content="获取完整歌曲" placement="top" v-if="item.ref_song">
                  <button class="btn btn-icon" @click="merge(item)">
                    <i class="iconfont icon-concat"></i>
                  </button>
                </el-tooltip>
                <el-tooltip content="复制歌曲链接" placement="top">
                  <button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(item)">
                    <i class="iconfont icon-share1"></i>
                  </button>
                </el-tooltip>
                <el-tooltip content="编辑" placement="top">
                  <button class="btn btn-icon" @click="update(item)">
                    <i class="iconfont icon-edit"></i>
                  </button>
                </el-tooltip>
                <el-tooltip content="删除" placement="top">
                  <button class="btn btn-icon" @click="removeJob(item)">
                    <i class="iconfont icon-remove"></i>
                  </button>
                </el-tooltip>
              </div>
            </div>
          </div>
          <!-- 正在运行和错误任务展示 -->
          <div class="task" v-else>
            <div class="left">
              <div class="title">
                <span v-if="item.title">{{ item.title }}</span>
                <span v-else>{{ item.prompt }}</span>
              </div>
            </div>
            <div class="center">
              <div class="failed" v-if="item.progress === 101">
                {{ item.err_msg }}
              </div>
              <generating v-else message="正在生成歌曲"/>
            </div>
            <div class="right">
              <el-button type="info" @click="removeJob(item)" circle>
                <i class="iconfont icon-remove"></i>
              </el-button>
            </div>
          </div>
        </div>
      </div>
      <el-empty :image-size="100" :image="nodata" description="没有任何作品，赶紧去创作吧！" v-else/>
      <!-- 分页展示 -->
      <div class="pagination">
        <el-pagination
            v-if="total > pageSize" background
            style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
            layout="total,prev, pager, next"
            :hide-on-single-page="true"
            v-model:current-page="page"
            v-model:page-size="pageSize"
            @current-change="fetchData(page)"
            :total="total"/>
      </div>
      <!-- 播放器展示 -->
      <div class="music-player" v-if="showPlayer">
        <music-player :songs="playList" ref="playerRef" :song-index="songIndex" :show-close="true" @close="showPlayer = false"/>
      </div>
    </div>
    <!-- 编辑弹框 -->
    <black-dialog v-model:show="showDialog" title="修改歌曲" @cancel="showDialog = false" @confirm="updateSong"
                  :width="500+'px'">
      <form class="form">
        <div class="form-item">
          <div class="label">歌曲名称</div>
          <el-input v-model="editData.title" type="text"/>
        </div>
        <div class="form-item">
          <div class="label">封面图片</div>
          <el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false" :http-request="uploadCover"
                     accept=".png,.jpg,.jpeg,.bmp">
            <el-avatar :src="editData.cover" shape="square" :size="100"/>
          </el-upload>
        </div>
      </form>
    </black-dialog>
  </div>
</template>

<style scoped lang="stylus">
@import "../assets/css/suno.styl"
</style>